/**!
 * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,
 * progress, resize, thumbnail, preview, validation and CORS
 * FileAPI Flash shim for old browsers not supporting FormData
 * @author  Danial  <danial.farid@gmail.com>
 * @version 12.2.13
 */

(function () {
    /** @namespace FileAPI.noContentTimeout */

    function patchXHR(fnName, newFn) {
        window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
    }

    function redefineProp(xhr, prop, fn) {
        try {
            Object.defineProperty(xhr, prop, {get: fn});
        } catch (e) {/*ignore*/
        }
    }

    if (!window.FileAPI) {
        window.FileAPI = {};
    }

    if (!window.XMLHttpRequest) {
        throw 'AJAX is not supported. XMLHttpRequest is not defined.';
    }

    FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;
    if (FileAPI.shouldLoad) {
        var initializeUploadListener = function (xhr) {
            if (!xhr.__listeners) {
                if (!xhr.upload) xhr.upload = {};
                xhr.__listeners = [];
                var origAddEventListener = xhr.upload.addEventListener;
                xhr.upload.addEventListener = function (t, fn) {
                    xhr.__listeners[t] = fn;
                    if (origAddEventListener) origAddEventListener.apply(this, arguments);
                };
            }
        };

        patchXHR('open', function (orig) {
            return function (m, url, b) {
                initializeUploadListener(this);
                this.__url = url;
                try {
                    orig.apply(this, [m, url, b]);
                } catch (e) {
                    if (e.message.indexOf('Access is denied') > -1) {
                        this.__origError = e;
                        orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);
                    }
                }
            };
        });

        patchXHR('getResponseHeader', function (orig) {
            return function (h) {
                return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));
            };
        });

        patchXHR('getAllResponseHeaders', function (orig) {
            return function () {
                return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));
            };
        });

        patchXHR('abort', function (orig) {
            return function () {
                return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
            };
        });

        patchXHR('setRequestHeader', function (orig) {
            return function (header, value) {
                if (header === '__setXHR_') {
                    initializeUploadListener(this);
                    var val = value(this);
                    // fix for angular < 1.2.0
                    if (val instanceof Function) {
                        val(this);
                    }
                } else {
                    this.__requestHeaders = this.__requestHeaders || {};
                    this.__requestHeaders[header] = value;
                    orig.apply(this, arguments);
                }
            };
        });

        patchXHR('send', function (orig) {
            return function () {
                var xhr = this;
                if (arguments[0] && arguments[0].__isFileAPIShim) {
                    var formData = arguments[0];
                    var config = {
                        url: xhr.__url,
                        jsonp: false, //removes the callback form param
                        cache: true, //removes the ?fileapiXXX in the url
                        complete: function (err, fileApiXHR) {
                            if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {
                                // this error seems to be fine the file is being uploaded properly.
                                err = null;
                            }
                            xhr.__completed = true;
                            if (!err && xhr.__listeners.load)
                                xhr.__listeners.load({
                                    type: 'load',
                                    loaded: xhr.__loaded,
                                    total: xhr.__total,
                                    target: xhr,
                                    lengthComputable: true
                                });
                            if (!err && xhr.__listeners.loadend)
                                xhr.__listeners.loadend({
                                    type: 'loadend',
                                    loaded: xhr.__loaded,
                                    total: xhr.__total,
                                    target: xhr,
                                    lengthComputable: true
                                });
                            if (err === 'abort' && xhr.__listeners.abort)
                                xhr.__listeners.abort({
                                    type: 'abort',
                                    loaded: xhr.__loaded,
                                    total: xhr.__total,
                                    target: xhr,
                                    lengthComputable: true
                                });
                            if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {
                                return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;
                            });
                            if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {
                                return fileApiXHR.statusText;
                            });
                            redefineProp(xhr, 'readyState', function () {
                                return 4;
                            });
                            if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {
                                return fileApiXHR.response;
                            });
                            var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);
                            redefineProp(xhr, 'responseText', function () {
                                return resp;
                            });
                            redefineProp(xhr, 'response', function () {
                                return resp;
                            });
                            if (err) redefineProp(xhr, 'err', function () {
                                return err;
                            });
                            xhr.__fileApiXHR = fileApiXHR;
                            if (xhr.onreadystatechange) xhr.onreadystatechange();
                            if (xhr.onload) xhr.onload();
                        },
                        progress: function (e) {
                            e.target = xhr;
                            if (xhr.__listeners.progress) xhr.__listeners.progress(e);
                            xhr.__total = e.total;
                            xhr.__loaded = e.loaded;
                            if (e.total === e.loaded) {
                                // fix flash issue that doesn't call complete if there is no response text from the server
                                var _this = this;
                                setTimeout(function () {
                                    if (!xhr.__completed) {
                                        xhr.getAllResponseHeaders = function () {
                                        };
                                        _this.complete(null, {status: 204, statusText: 'No Content'});
                                    }
                                }, FileAPI.noContentTimeout || 10000);
                            }
                        },
                        headers: xhr.__requestHeaders
                    };
                    config.data = {};
                    config.files = {};
                    for (var i = 0; i < formData.data.length; i++) {
                        var item = formData.data[i];
                        if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {
                            config.files[item.key] = item.val;
                        } else {
                            config.data[item.key] = item.val;
                        }
                    }

                    setTimeout(function () {
                        if (!FileAPI.hasFlash) {
                            throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
                        }
                        xhr.__fileApiXHR = FileAPI.upload(config);
                    }, 1);
                } else {
                    if (this.__origError) {
                        throw this.__origError;
                    }
                    orig.apply(xhr, arguments);
                }
            };
        });
        window.XMLHttpRequest.__isFileAPIShim = true;
        window.FormData = FormData = function () {
            return {
                append: function (key, val, name) {
                    if (val.__isFileAPIBlobShim) {
                        val = val.data[0];
                    }
                    this.data.push({
                        key: key,
                        val: val,
                        name: name
                    });
                },
                data: [],
                __isFileAPIShim: true
            };
        };

        window.Blob = Blob = function (b) {
            return {
                data: b,
                __isFileAPIBlobShim: true
            };
        };
    }

})();

(function () {
    /** @namespace FileAPI.forceLoad */

    /** @namespace window.FileAPI.jsUrl */
    /** @namespace window.FileAPI.jsPath */

    function isInputTypeFile(elem) {
        return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';
    }

    function hasFlash() {
        try {
            var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
            if (fo) return true;
        } catch (e) {
            if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;
        }
        return false;
    }

    function getOffset(obj) {
        var left = 0, top = 0;

        if (window.jQuery) {
            return jQuery(obj).offset();
        }

        if (obj.offsetParent) {
            do {
                left += (obj.offsetLeft - obj.scrollLeft);
                top += (obj.offsetTop - obj.scrollTop);
                obj = obj.offsetParent;
            } while (obj);
        }
        return {
            left: left,
            top: top
        };
    }

    if (FileAPI.shouldLoad) {
        FileAPI.hasFlash = hasFlash();

        //load FileAPI
        if (FileAPI.forceLoad) {
            FileAPI.html5 = false;
        }

        if (!FileAPI.upload) {
            var jsUrl, basePath, script = document.createElement('script'),
                allScripts = document.getElementsByTagName('script'), i, index, src;
            if (window.FileAPI.jsUrl) {
                jsUrl = window.FileAPI.jsUrl;
            } else if (window.FileAPI.jsPath) {
                basePath = window.FileAPI.jsPath;
            } else {
                for (i = 0; i < allScripts.length; i++) {
                    src = allScripts[i].src;
                    index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/);
                    if (index > -1) {
                        basePath = src.substring(0, index + 1);
                        break;
                    }
                }
            }

            if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;
            script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');
            document.getElementsByTagName('head')[0].appendChild(script);
        }

        FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {
            if (!hasFlash()) {
                throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
            }
            var fixInputStyle = function () {
                var label = fileElem.parent();
                if (elem.attr('disabled')) {
                    if (label) label.removeClass('js-fileapi-wrapper');
                } else {
                    if (!fileElem.attr('__ngf_flash_')) {
                        fileElem.unbind('change');
                        fileElem.unbind('click');
                        fileElem.bind('change', function (evt) {
                            fileApiChangeFn.apply(this, [evt]);
                            changeFn.apply(this, [evt]);
                        });
                        fileElem.attr('__ngf_flash_', 'true');
                    }
                    label.addClass('js-fileapi-wrapper');
                    if (!isInputTypeFile(elem)) {
                        label.css('position', 'absolute')
                            .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')
                            .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
                            .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))
                            .css('overflow', 'hidden').css('z-index', '900000')
                            .css('visibility', 'visible');
                        fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
                            .css('position', 'absolute').css('top', '0px').css('left', '0px');
                    }
                }
            };

            elem.bind('mouseenter', fixInputStyle);

            var fileApiChangeFn = function (evt) {
                var files = FileAPI.getFiles(evt);
                //just a double check for #233
                for (var i = 0; i < files.length; i++) {
                    if (files[i].size === undefined) files[i].size = 0;
                    if (files[i].name === undefined) files[i].name = 'file';
                    if (files[i].type === undefined) files[i].type = 'undefined';
                }
                if (!evt.target) {
                    evt.target = {};
                }
                evt.target.files = files;
                // if evt.target.files is not writable use helper field
                if (evt.target.files !== files) {
                    evt.__files_ = files;
                }
                (evt.__files_ || evt.target.files).item = function (i) {
                    return (evt.__files_ || evt.target.files)[i] || null;
                };
            };
        };

        FileAPI.disableFileInput = function (elem, disable) {
            if (disable) {
                elem.removeClass('js-fileapi-wrapper');
            } else {
                elem.addClass('js-fileapi-wrapper');
            }
        };
    }
})();

if (!window.FileReader) {
    window.FileReader = function () {
        var _this = this, loadStarted = false;
        this.listeners = {};
        this.addEventListener = function (type, fn) {
            _this.listeners[type] = _this.listeners[type] || [];
            _this.listeners[type].push(fn);
        };
        this.removeEventListener = function (type, fn) {
            if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);
        };
        this.dispatchEvent = function (evt) {
            var list = _this.listeners[evt.type];
            if (list) {
                for (var i = 0; i < list.length; i++) {
                    list[i].call(_this, evt);
                }
            }
        };
        this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;

        var constructEvent = function (type, evt) {
            var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};
            if (evt.result != null) e.target.result = evt.result;
            return e;
        };
        var listener = function (evt) {
            if (!loadStarted) {
                loadStarted = true;
                if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));
            }
            var e;
            if (evt.type === 'load') {
                if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));
                e = constructEvent('load', evt);
                if (_this.onload) _this.onload(e);
                _this.dispatchEvent(e);
            } else if (evt.type === 'progress') {
                e = constructEvent('progress', evt);
                if (_this.onprogress) _this.onprogress(e);
                _this.dispatchEvent(e);
            } else {
                e = constructEvent('error', evt);
                if (_this.onerror) _this.onerror(e);
                _this.dispatchEvent(e);
            }
        };
        this.readAsDataURL = function (file) {
            FileAPI.readAsDataURL(file, listener);
        };
        this.readAsText = function (file) {
            FileAPI.readAsText(file, listener);
        };
    };
}
